#!/usr/bin/python
# -*- coding: latin1 -*-
"""

    Name: reader.py

    Author: Enrico Bortolazzi - Veia Progetti s.r.l.

    Comment: brailleMusicML reader

    Version: 1.0

    Date: Fri Aug 24, 2007

------------------------------------------------------------------------

Copyright (c) 2007, Veia Progetti s.r.l. - http://www.veia.it

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

    Redistributions of source code must retain the above copyright notice,
    this list of conditions and the following disclaimer.

    Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in
    the documentation and/or other materials provided with the distribution.

    Neither name of Veia Progetti s.r.l. nor the name of its contributors
    may be used to endorse or promote products derived from this software
    without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""
import os, sys
import xml.sax
from xml.sax import ContentHandler, SAXException
from xml.sax.handler import EntityResolver

import _brailleMusicML

class _BrailleMusicMLHandler(ContentHandler):
    """ Handler for Braille Music ML
        prefactory: function used for prebuild the element as
            prefactory(classname, **attrs) returning an object
        postfactory: function used for finalize the element as
            postfactory(obj) with obj the object returned by prefactory
        processing: processing instructions handler
            processing(obj, target, data) with obj the object returned by prefactory
    """
    def __init__(self, prefactory=None, postfactory=None, processing=None):
        ContentHandler.__init__(self)
        self._stack = []
        self.root = None
        #redirect object factory if requested
        self.prefactory = prefactory if prefactory else self._factory
        self.postfactory = postfactory if postfactory else self._postfactory
        self.processing = processing
        self.max_id = 0  #max id value read (generator in _brailleMusicML starts form here)
        self.version = '0.0'

    def _factory(self, classname, **attrs):
        """ object factory: return the new created object """
        cls = getattr(_brailleMusicML, classname, None)
        if not cls:
            raise NameError, '_brailleMusicML module has no class %s' % classname
        #create class instance
        if issubclass(cls, _brailleMusicML._ElementText):
            attrs['text'] = ''
        try:
            return cls(**attrs)
        except:
            raise sys.exc_type(sys.exc_value.message + ': <%s>' % (classname.lower()))

    def _postfactory(self, obj):
        pass

    def _value_cnv(self, value):
        """ return value converted
            from string to int, float, bool, string
        """
        try:
            v = int(value)
        except:
            try:
                v = float(value)
            except:
                if value.lower() == 'true': v = True
                elif value.lower() == 'false': v= False
                else: v = value
        return v

    def _buildElement(self, m_name, attributes):
        """ Create new element with attributes and value if needed """
        classname = m_name.capitalize()
        ##attrs = dict([(name_mangle(str(a)),v) for a,v in attributes.items()])
        attrs = {}
        for a,v in attributes.items():
            name = _brailleMusicML.name_mangle(str(a))
            attrs[name] = self._value_cnv(v)
        #--#if 'id_' in attrs:
        #--#    try:
        #--#        id_ = int(attrs['id_'].strip(_brailleMusicML.default_id_prefix))
        #--#        if id_ > self.max_id:
        #--#            self.max_id = id_
        #--#    except:
        #--##        pass
        return self.prefactory(classname, **attrs)

    def _getFunctions(self, parent, m_name):
        """ return get and insert functions """
        getf = getattr(parent, 'get_%s' % m_name, None)
        if getf:
            if m_name in parent._elements:
                setf = getattr(parent, 'set_%s' % m_name, None)
            elif m_name in parent._elements_ls:
                setf = getattr(parent, 'add_%s' % m_name, None)
            else:
                setf = None
        if not getf or not setf:
            raise ValueError, '%s has no insert function for element <%s>' % (parent.xml_name(), m_name)
        return getf, setf

    def startElement(self, name, attributes):
        #update to last version
        name,attributes = self._startUpdateVersion(name,attributes)
        self._startElement(name, attributes)

    def _startElement(self, name, attributes):
        m_name = _brailleMusicML.name_mangle(name)
        if self._stack:
            parent = self._stack[-1]
        else:
            parent = None
        #build a new element and attach to the parent
        element = self._buildElement(m_name, attributes)
        if self.root is None:
            self.root = element
        else:
            getf,setf = self._getFunctions(parent, m_name)  #get insert function
            setf(element)
        self._stack.append(element)

    def endElement(self, name):
        name = self._endUpdateVersion(name)
        self._endElement(name)

    def _endElement(self, name):
        obj = self._stack[-1]
        self.postfactory(obj)
        #empty stack
        while True:
            e = self._stack.pop()
            if hasattr(e, 'xml_name'):
                if e.xml_name() == str(name): break
            elif e == str(name):
                break

    def characters(self, content):
        obj = self._stack[-1]
        #if hasattr(obj, 'text'):
        if isinstance(obj, _brailleMusicML._ElementText):
            obj.text += unicode(content)
        #elif hasattr(obj, 'value'):
        elif isinstance(obj, _brailleMusicML._ElementValue):
            #obj.value = int(content)
            obj.value = self._value_cnv(content)

    def processingInstruction(self, target, data):
        #print 'processingInstruction', target, data
        if callable(self.processing):
            try:
                obj = self._stack[-1]
                self.processing(obj, target, data)
            except:
                pass

    def _startUpdateVersion(self, name, attributes):
        if name == 'score':
            if 'version' in attributes:
                self.version = attributes['version']
            else:
                self.version = '0.0'

        if self.version < '0.8':
            #version 0.7->0.8
            if name == 'barline_left' or name == 'barline':
                if name == 'barline_left':
                    attrs = {'location':'left'}
                else:
                    attrs = {}
                if 'id' in attributes:
                    attrs['id'] = attributes['id']
                    attributes = dict(attributes)
                    attributes['id'] = attributes['id'] + '-1'
                self._startElement('barline',attrs)
                return 'barline_type',attributes
        return name,attributes

    def _endUpdateVersion(self, name):
        if self.version < '0.8':
        #version 0.7->0.8
            if name == 'barline_left' or name == 'barline':
                self._endElement('barline_type')
                return 'barline'
        return name


class _BrailleMusicMLEntityResolver(EntityResolver):

    def resolveEntity(self, publicId, systemId):
        if os.path.isfile(systemId):
            return systemId
        dir,fname = os.path.split(systemId)
        return os.path.abspath(os.path.dirname(__file__)+'/dtd/'+fname)


def read(filename_or_stream,
        prefactory=None,
        postfactory=None,
        processing=None,
        set_references=True):
    handler = _BrailleMusicMLHandler(prefactory, postfactory, processing)
    parser = xml.sax.make_parser()
    parser.setContentHandler(handler)
    #avoid connection to the net to resolve entities
    parser.setEntityResolver(_BrailleMusicMLEntityResolver())
    parser.parse(filename_or_stream)
    if set_references:
        resolve_references(handler.root)
    #--#_brailleMusicML.init_xml_id(handler.max_id + 1)  #init id generator
    return handler.root


def resolve_references(root):
    """ resolve references """
    referents = {}
    references = {}
    stack = [root]
    while stack:
        e = stack.pop()
        if isinstance(e, _brailleMusicML._ElementProxy):
            if e.id_ not in references:
                references[e.id_] = []
            references[e.id_].append(e)
        else:
            if hasattr(e, 'id_'):
                referents[e.id_] = e
        if hasattr(e, '_is_container') and e._is_container:
            for i in e:
                stack.append(i)
    unbound = []
    for i,xs in references.iteritems():
        if i in referents:
            for e in xs:
                e.bind(referents[i])
        else:
            unbound.append(i)

    if unbound:
        raise ReferenceError('Some elements are not bound: %s' % str(unbound))

